package org.glut.gateway.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.server.ServerWebExchange;

import java.time.Duration;
import java.util.function.Function;

/**
 * <pre>
 * Spring Cloud Gateway是一个基于Spring Boot的API网关，提供了统一的访问入口，可以通过网关层面实现诸如限流、熔断等功能，从而保护后端服务。
 *
 * 限流（Rate limiting）是一种保护服务免受过度使用或滥用的方法，它可以控制请求的数量和速率，以防止超出服务能力的范围。
 * Spring Cloud Gateway提供了多种限流方式，包括基于Redis、基于令牌桶和基于漏桶算法等。
 *
 * 该过滤器的核心逻辑是：首先从请求头中获取一个标识符，然后使用这个标识符构造一个Redis键，使用Redis对这个键进行计数。
 * 如果当前计数小于等于允许的请求数量，则将计数减1，并继续执行后续过滤器和请求处理程序。否则，抛出一个限流异常。
 * </pre>
 *
 * @author jamesmac
 */
public class RedisRateLimiterGatewayFilterFactory
        extends AbstractGatewayFilterFactory<RedisRateLimiterGatewayFilterFactory.Config> {

    @Autowired
    RedisTemplate redisTemplate;

    public RedisRateLimiterGatewayFilterFactory(/*RedisTemplate redisTemplate*/) {
        super(Config.class);
        // this.redisTemplate = redisTemplate;
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String key = exchange.getRequest().getHeaders().getFirst(config.getHeader());
            if (key == null) {
                throw new IllegalArgumentException("Header " + config.getHeader() + " not found");
            }

            String redisKey = config.getKeyResolver().apply(exchange);
            String permits = String.valueOf(redisTemplate.opsForValue().get(redisKey));
            if (permits == null) {
                redisTemplate.opsForValue().set(redisKey, String.valueOf(config.getPermits()));
                redisTemplate.expire(redisKey, config.getDuration());
            } else {
                int remaining = Integer.parseInt(permits) - 1;
                if (remaining < 0) {
                    throw new RateLimiterException();
                }
                redisTemplate.opsForValue().set(redisKey, String.valueOf(remaining));
            }
            return chain.filter(exchange);
        };
    }

    public static class Config {
        private String header = "X-RateLimit-Header";
        private int permits = 10;
        private Duration duration = Duration.ofSeconds(1);
        private Function<ServerWebExchange, String> keyResolver = exchange -> exchange.getRequest().getPath().toString();

        public String getHeader() {
            return header;
        }

        public void setHeader(String header) {
            this.header = header;
        }

        public int getPermits() {
            return permits;
        }

        public void setPermits(int permits) {
            this.permits = permits;
        }

        public Duration getDuration() {
            return duration;
        }

        public void setDuration(Duration duration) {
            this.duration = duration;
        }

        public Function<ServerWebExchange, String> getKeyResolver() {
            return keyResolver;
        }

        public void setKeyResolver(Function<ServerWebExchange, String> keyResolver) {
            this.keyResolver = keyResolver;
        }
    }

    public static class RateLimiterException extends RuntimeException {
        public RateLimiterException() {
            super("Rate limit exceeded");
        }
    }
}